{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "### 数据处理"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "# 安装类库\n",
    "# !mkdir /home/aistudio/external-libraries\n",
    "# !pip install imgaug -t /home/aistudio/external-libraries\n",
    "import sys\n",
    "sys.path.append('/home/aistudio/external-libraries')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "image shape: (32, 32, 3)\n",
      "label value: horse\n"
     ]
    },
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 216x216 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "import paddle\n",
    "import numpy as np\n",
    "from PIL import Image\n",
    "import matplotlib.pyplot as plt\n",
    "import imgaug as ia\n",
    "import imgaug.augmenters as iaa\n",
    "\n",
    "# 读取数据\n",
    "reader = paddle.batch(\n",
    "    paddle.dataset.cifar.train10(),\n",
    "    batch_size=8) # 数据集读取器\n",
    "data = next(reader()) # 读取数据\n",
    "index = 4 # 批次索引\n",
    "\n",
    "# 读取图像\n",
    "image = np.array([x[0] for x in data]).astype(np.float32) # 读取图像数据，数据类型为float32\n",
    "image = image * 255 # 从[0,1]转换到[0,255]\n",
    "image = image[index].reshape((3, 32, 32)).transpose((1, 2, 0)).astype(np.uint8) # 数据格式从CHW转换为HWC，数据类型转换为uint8\n",
    "print('image shape:', image.shape)\n",
    "\n",
    "# 图像增强\n",
    "# sometimes = lambda aug: iaa.Sometimes(0.5, aug) # 随机进行图像增强\n",
    "# seq = iaa.Sequential([\n",
    "#     sometimes(iaa.CropAndPad(px=(-4, 4))),      # 随机裁剪填充像素\n",
    "#     iaa.Fliplr(0.5)])                           # 随机进行水平翻转\n",
    "# image = seq(image=image)\n",
    "\n",
    "# 读取标签\n",
    "label = np.array([x[1] for x in data]).astype(np.int64) # 读取标签数据，数据类型为int64\n",
    "vlist = [\"airplane\", \"automobile\", \"bird\", \"cat\", \"deer\", \"dog\", \"frog\", \"horse\", \"ship\", \"truck\"] # 标签名称列表\n",
    "print('label value:', vlist[label[index]])\n",
    "\n",
    "# 显示图像\n",
    "image = Image.fromarray(image)   # 转换图像格式\n",
    "image.save('./work/out/img.png') # 保存读取图像\n",
    "plt.figure(figsize=(3, 3))       # 设置显示大小\n",
    "plt.imshow(image)                # 设置显示图像\n",
    "plt.show()                       # 显示图像文件"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "train_data: image shape (128, 3, 32, 32), label shape:(128, 1)\n",
      "valid_data: image shape (128, 3, 32, 32), label shape:(128, 1)\n"
     ]
    }
   ],
   "source": [
    "import paddle\n",
    "import numpy as np\n",
    "import imgaug as ia\n",
    "import imgaug.augmenters as iaa\n",
    "\n",
    "# 训练数据增强\n",
    "def train_augment(images):\n",
    "    # 转换格式\n",
    "    images = images * 255 # 从[0,1]转换到[0,255]\n",
    "    images = images.transpose((0, 2, 3, 1)).astype(np.uint8) # 数据格式从BCHW转换为BHWC，数据类型转换为uint8\n",
    "    \n",
    "    # 增强图像\n",
    "    sometimes = lambda aug: iaa.Sometimes(0.5, aug) # 随机进行图像增强\n",
    "    seq = iaa.Sequential([\n",
    "        sometimes(iaa.CropAndPad(px=(-4, 4))),      # 随机裁剪填充像素\n",
    "        iaa.Fliplr(0.5)])                           # 随机进行水平翻转\n",
    "    images = seq(images=images)\n",
    "    \n",
    "    # 减去均值\n",
    "    mean = np.array([0.4914, 0.4822, 0.4465]).reshape((1, 1, 1, -1)) # cifar数据集通道平均值\n",
    "    stdv = np.array([0.2471, 0.2435, 0.2616]).reshape((1, 1, 1, -1)) # cifar数据集通道标准差\n",
    "    \n",
    "    images = (images/255.0 - mean) / stdv # 对图像进行归一化\n",
    "    images = images.transpose((0, 3, 1, 2)).astype(np.float32) # 数据格式从BHWC转换为BCHW，数据类型转换为float32\n",
    "    \n",
    "    return images\n",
    "\n",
    "# 验证数据增强\n",
    "def valid_augment(images):\n",
    "    # 转换格式\n",
    "    images = images * 255 # 从[0,1]转换到[0,255]\n",
    "    images = images.transpose((0, 2, 3, 1)).astype(np.uint8) # 数据格式从BCHW转换为BHWC，数据类型转换为uint8\n",
    "    \n",
    "    # 减去均值\n",
    "    mean = np.array([0.4914, 0.4822, 0.4465]).reshape((1, 1, 1, -1)) # cifar数据集通道平均值\n",
    "    stdv = np.array([0.2471, 0.2435, 0.2616]).reshape((1, 1, 1, -1)) # cifar数据集通道标准差\n",
    "    \n",
    "    images = (images/255.0 - mean) / stdv # 对图像进行归一化\n",
    "    images = images.transpose((0, 3, 1, 2)).astype(np.float32) # 数据格式从BHWC转换为BCHW，数据类型转换为float32\n",
    "    \n",
    "    return images\n",
    "\n",
    "# 读取训练数据\n",
    "train_reader = paddle.batch(\n",
    "    paddle.reader.shuffle(paddle.dataset.cifar.train10(), buf_size=50000),\n",
    "    batch_size=128) # 构造数据读取器\n",
    "train_data = next(train_reader()) # 读取训练数据\n",
    "\n",
    "train_image = np.array([x[0] for x in train_data]).reshape((-1, 3, 32, 32)).astype(np.float32) # 读取训练图像\n",
    "train_image = train_augment(train_image)                                                       # 训练图像增强\n",
    "train_label = np.array([x[1] for x in train_data]).reshape((-1, 1)).astype(np.int64)           # 读取训练标签\n",
    "print('train_data: image shape {}, label shape:{}'.format(train_image.shape, train_label.shape))\n",
    "\n",
    "# 读取验证数据\n",
    "valid_reader = paddle.batch(\n",
    "    paddle.dataset.cifar.test10(),\n",
    "    batch_size=128) # 构造数据读取器\n",
    "valid_data = next(valid_reader()) # 读取验证数据\n",
    "\n",
    "valid_image = np.array([x[0] for x in valid_data]).reshape((-1, 3, 32, 32)).astype(np.float32) # 读取验证图像\n",
    "valid_image = valid_augment(valid_image)                                                       # 验证图像增强\n",
    "valid_label = np.array([x[1] for x in valid_data]).reshape((-1, 1)).astype(np.int64)           # 读取验证标签\n",
    "print('valid_data: image shape {}, label shape:{}'.format(valid_image.shape, valid_label.shape))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "### 模型设计"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "import paddle.fluid as fluid\n",
    "from paddle.fluid.dygraph.nn import Conv2D, Pool2D, Linear, BatchNorm\n",
    "import math\n",
    "\n",
    "# 模组结构：输入维度，输出维度，滑动步长，基础长度\n",
    "group_arch = [(16, 16, 1, 3), (16, 32, 2, 3), (32, 64, 2, 3)]\n",
    "group_dim  = 64 # 模组输出维度\n",
    "class_dim  = 10 # 类别数量维度\n",
    "\n",
    "# 卷积单元\n",
    "class ConvUnit(fluid.dygraph.Layer):\n",
    "    def __init__(self, in_dim, out_dim, filter_size=3, stride=1, act=None):\n",
    "        \"\"\"\n",
    "        功能:\n",
    "            初始化卷积单元，H/W=(H/W+2*P-F)/S+1\n",
    "        输入:\n",
    "            in_dim      - 输入维度\n",
    "            out_dim     - 输出维度\n",
    "            filter_size - 卷积大小\n",
    "            stride      - 滑动步长\n",
    "            act         - 激活函数\n",
    "        输出:\n",
    "        \"\"\"\n",
    "        super(ConvUnit, self).__init__()\n",
    "        \n",
    "        # 添加卷积\n",
    "        self.conv = Conv2D(\n",
    "            num_channels=in_dim,\n",
    "            num_filters=out_dim,\n",
    "            filter_size=filter_size,\n",
    "            stride=stride,\n",
    "            padding=(filter_size-1)//2,                       # 输出特征图大小不变\n",
    "            param_attr=fluid.initializer.MSRA(uniform=False), # 使用MARA 初始权重\n",
    "            bias_attr=False,                                  # 卷积输出没有偏置项\n",
    "            act=None)\n",
    "        \n",
    "        # 添加正则\n",
    "        self.norm = BatchNorm(\n",
    "            num_channels=out_dim,\n",
    "            param_attr=fluid.initializer.Constant(1.0), # 使用常量初始化权重\n",
    "            bias_attr=fluid.initializer.Constant(0.0),  # 使用常量初始化偏置\n",
    "            act=act)\n",
    "    \n",
    "    def forward(self, x):\n",
    "        \"\"\"\n",
    "        功能:\n",
    "            对输入的特征进行卷积和正则\n",
    "        输入:\n",
    "            x - 输入特征\n",
    "        输出:\n",
    "            x - 输出特征\n",
    "        \"\"\"\n",
    "        # 进行卷积\n",
    "        x = self.conv(x)\n",
    "        \n",
    "        # 进行正则\n",
    "        x = self.norm(x)\n",
    "        \n",
    "        return x\n",
    "\n",
    "\n",
    "# 基础结构\n",
    "class ResBasic(fluid.dygraph.Layer):\n",
    "    def __init__(self, in_dim, out_dim, stride=1, is_pass=True):\n",
    "        \"\"\"\n",
    "        功能:\n",
    "            初始化基础结构，H/W=(H/W+2*P-F)/S+1\n",
    "        输入:\n",
    "            in_dim  - 输入维度\n",
    "            out_dim - 输出维度\n",
    "            stride  - 滑动步长\n",
    "            is_pass - 是否直连\n",
    "        输出:\n",
    "        \"\"\"\n",
    "        super(ResBasic, self).__init__()\n",
    "        \n",
    "        # 是否直连标识\n",
    "        self.is_pass = is_pass\n",
    "        \n",
    "        # 添加投影路径\n",
    "        self.proj = ConvUnit(in_dim=in_dim, out_dim=out_dim, filter_size=1, stride=stride, act=None)\n",
    "        \n",
    "        # 添加卷积路径\n",
    "        self.con1 = ConvUnit(in_dim=in_dim, out_dim=out_dim, filter_size=3, stride=stride, act='relu')\n",
    "        self.con2 = ConvUnit(in_dim=out_dim, out_dim=out_dim, filter_size=3, stride=1, act=None)\n",
    "        \n",
    "    def forward(self, x):\n",
    "        \"\"\"\n",
    "        功能:\n",
    "            对输入的特征图像提取特征\n",
    "        输入:\n",
    "            x - 输入特征\n",
    "        输出:\n",
    "            x - 输出特征\n",
    "            y - 输出特征\n",
    "        \"\"\"\n",
    "        # 直连路径\n",
    "        if self.is_pass: # 是否直连\n",
    "            x_pass = x\n",
    "        else:            # 否则投影\n",
    "            x_pass = self.proj(x)\n",
    "        \n",
    "        # 卷积路径\n",
    "        x_con1 = self.con1(x)\n",
    "        x_con2 = self.con2(x_con1)\n",
    "        \n",
    "        # 输出特征\n",
    "        x = fluid.layers.elementwise_add(x=x_pass, y=x_con1, act='relu') # 直连路径与卷积路径进行特征相加\n",
    "        \n",
    "        return x\n",
    "    \n",
    "# 模块结构\n",
    "class ResBlock(fluid.dygraph.Layer):\n",
    "    def __init__(self, in_dim, out_dim, stride=1, basics=1):\n",
    "        \"\"\"\n",
    "        功能:\n",
    "            初始化模块结构，H/W=(H/W+2*P-F)/S+1\n",
    "        输入:\n",
    "            in_dim  - 输入维度\n",
    "            out_dim - 输出维度\n",
    "            stride  - 滑动步长\n",
    "            basics  - 基础长度\n",
    "        输出:\n",
    "        \"\"\"\n",
    "        super(ResBlock, self).__init__()\n",
    "        \n",
    "        # 添加模块列表\n",
    "        self.block_list = [] # 模块列表\n",
    "        for i in range(basics):\n",
    "            block_item = self.add_sublayer( # 构造模块项目\n",
    "                'block_' + str(i),\n",
    "                ResBasic(\n",
    "                    in_dim=(in_dim if i==0 else out_dim), # 每组模块项目除第一块外，输入维度=输出维度\n",
    "                    out_dim=out_dim,\n",
    "                    stride=(stride if i==0 else 1), # 每组模块项目除第一块外，stride=1\n",
    "                    is_pass=(False if i==0 else True))) # 每组模块项目除第一块外，is_pass=True\n",
    "            self.block_list.append(block_item) # 添加模块项目\n",
    "    \n",
    "    def forward(self, x):\n",
    "        \"\"\"\n",
    "        功能:\n",
    "            对输入的特征图像提取特征\n",
    "        输入:\n",
    "            x      - 输入特征\n",
    "        输出:\n",
    "            x      - 输出特征\n",
    "        \"\"\"\n",
    "        for block_item in self.block_list:\n",
    "            x = block_item(x) # 提取模块特征\n",
    "            \n",
    "        return x\n",
    "\n",
    "# 模组结构\n",
    "class ResGroup(fluid.dygraph.Layer):\n",
    "    def __init__(self):\n",
    "        \"\"\"\n",
    "        功能:\n",
    "            初始化模组结构，H/W=(H/W+2*P-F)/S+1\n",
    "        输入:\n",
    "        输出:\n",
    "        \"\"\"\n",
    "        super(ResGroup, self).__init__()\n",
    "        \n",
    "        # 添加模组列表\n",
    "        self.group_list = [] # 模组列表\n",
    "        for i, block_arch in enumerate(group_arch):\n",
    "            group_item = self.add_sublayer( # 构造模组项目\n",
    "                'group_' + str(i),\n",
    "                ResBlock(\n",
    "                    in_dim=block_arch[0],\n",
    "                    out_dim=block_arch[1],\n",
    "                    stride=block_arch[2],\n",
    "                    basics=block_arch[3]))\n",
    "            self.group_list.append(group_item) # 添加模组项目\n",
    "    \n",
    "    def forward(self, x):\n",
    "        \"\"\"\n",
    "        功能:\n",
    "            对输入的特征图像提取特征\n",
    "        输入:\n",
    "            x      - 输入特征\n",
    "        输出:\n",
    "            x      - 输出特征\n",
    "        \"\"\"\n",
    "        for group_item in self.group_list:\n",
    "            x = group_item(x) # 提取模组特征\n",
    "            \n",
    "        return x\n",
    "        \n",
    "# 残差网络\n",
    "class ResNet(fluid.dygraph.Layer):\n",
    "    def __init__(self):\n",
    "        \"\"\"\n",
    "        功能:\n",
    "            初始化残差网络，H/W=(H/W+2*P-F)/S+1\n",
    "        输入:\n",
    "        输出:\n",
    "        \"\"\"\n",
    "        super(ResNet, self).__init__()\n",
    "        \n",
    "        # 添加初始化层\n",
    "        self.conv = ConvUnit(in_dim=3, out_dim=16, filter_size=3, stride=1, act='relu')\n",
    "        \n",
    "        # 添加模组结构\n",
    "        self.backbone = ResGroup() # 输出：N*C*H*W\n",
    "        \n",
    "        # 添加全连接层\n",
    "        self.pool = Pool2D(global_pooling=True, pool_type='avg') # 输出：N*C*1*1\n",
    "        \n",
    "        stdv = 1.0/(math.sqrt(group_dim)*1.0)                    # 设置均匀分布权重方差\n",
    "        self.fc = Linear(                                        # 输出：=N*10\n",
    "            input_dim=group_dim,\n",
    "            output_dim=class_dim,\n",
    "            param_attr=fluid.initializer.Uniform(-stdv, stdv),   # 使用均匀分布初始权重\n",
    "            bias_attr=fluid.initializer.Constant(0.0),           # 使用常量数值初始偏置\n",
    "            act='softmax')\n",
    "    \n",
    "    def forward(self, x):\n",
    "        \"\"\"\n",
    "        功能:\n",
    "            对输入图像进行分类\n",
    "        输入:\n",
    "            x - 输入图像\n",
    "        输出:\n",
    "            x - 预测结果\n",
    "        \"\"\"\n",
    "        # 提取特征\n",
    "        x = self.conv(x)\n",
    "        x = self.backbone(x)\n",
    "        \n",
    "        # 进行预测\n",
    "        x = self.pool(x)\n",
    "        x = fluid.layers.reshape(x, [x.shape[0], -1])\n",
    "        x = self.fc(x)\n",
    "        \n",
    "        return x"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "tatol param: 286010\n",
      "infer shape: [1, 10]\n"
     ]
    }
   ],
   "source": [
    "import paddle.fluid as fluid\n",
    "from paddle.fluid.dygraph.base import to_variable\n",
    "import numpy as np\n",
    "\n",
    "with fluid.dygraph.guard():\n",
    "    # 输入数据\n",
    "    x = np.random.randn(1, 3, 32, 32).astype(np.float32)\n",
    "    x = to_variable(x)\n",
    "    \n",
    "    # 进行预测\n",
    "    backbone = ResNet() # 设置网络\n",
    "    \n",
    "    infer = backbone(x) # 进行预测\n",
    "    \n",
    "    # 显示结果\n",
    "    parameters = 0\n",
    "    for p in backbone.parameters():\n",
    "        parameters += np.prod(p.shape) # 统计参数\n",
    "    \n",
    "    print('tatol param:', parameters)\n",
    "    print('infer shape:', infer.shape)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "### 训练模型"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "data": {
      "image/png": "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Z9iwYSUmo73++uvcc889pR4XHR0NwJEjR7jxxhv99hk6dCjFU3BLaxcEQThThJQonEnGjh3LzJkzfdpmzpzJ2LFjgzq+efPmfPHFF2diaIIgCFVGSInCmVpPAeDGG29kzpw57kV1Dhw4wJEjRxg0aBBZWVkMHz6c3r170717d77++usSxx84cIBu3boBkJuby5gxY+jcuTPXX389ubm5ZV5/xowZdO/enW7duvGPf/wDAIfDwR/+8Ae6detG9+7dee211wB444036NKlCz169HAX0hMEQQiG2lnmYt4EOLa5RHPzQof5Em4v/zmbdodRkwPujouLo2/fvsybN4/Ro0czc+ZMfve736GUIioqitmzZxMbG8uJEyfo378/1157bcC1kN955x3q1q3L9u3b2bRpE7179y51aEeOHOEf//gHa9eupWHDhowYMYKvvvqKVq1akZyczJYtZjE7V/nsyZMns3//fiIjI91tgiAIwRBSlsKZxtuF5O060lrz2GOP0aNHDy677DKSk5M5fvx4wPMsW7aMm2++GYAePXrQo0ePUq/766+/MnToUBo3bkxYWBjjxo1j2bJltG/fnn379nH//ffz/fffExsb6z7nuHHj+PjjjwkLq526LwhC9VDmE0Mp1QqYjllaUwNTtdZTivVRwBTgSiAH+IPWep217zbgcavrJK31fys96gBv9EdTs3BqOK9JdKUv4Y/Ro0fz0EMPsW7dOnJycujTpw8An3zyCampqaxdu5bw8HDatm3rt2R2VdOwYUM2btzI/Pnzeffdd/n888+ZNm0ac+bMYdmyZXz77bc899xzbN68WcRBEISgCMZSKAL+qrXuAvQH7lVKdSnWZxTQ0fqMB94BUErFAU8B/YC+wFNKqYZVNPYSKKXOaJmL6Ohohg0bxh133OETYE5PT6dJkyaEh4ezZMkSDh48WOp5Bg8ezKeffgrAli1byixv3bdvX5YuXcqJEydwOBzMmDGDIUOGcOLECZxOJzfccAOTJk1i3bp1OJ1ODh8+zLBhw3jxxRdJT08nKyur8jcvCMI5QZmvj1rro8BR63umUmo70ALwXlJsNDBdm3zQX5RSDZRSzYChwEKt9SkApdRCYCQwo0rvwkLBGZ+oMHbsWK6//nqfTKRx48ZxzTXX0L17dxITE+nUqVOp57jnnnu4/fbb6dy5M507d3ZbHIFo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\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "complete - train time: 5839s, best epoch: 266, best loss: 0.333661, best accuracy: 90.30%\r"
     ]
    },
    {
     "data": {
      "text/plain": [
       "<Figure size 432x288 with 0 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "import paddle\n",
    "import paddle.fluid as fluid\n",
    "from paddle.utils.plot import Ploter\n",
    "import numpy as np\n",
    "import time\n",
    "import math\n",
    "import os\n",
    "\n",
    "epoch_num = 300   # 训练周期，取值一般为[1,300]\n",
    "train_batch = 128 # 训练批次，取值一般为[1,256]\n",
    "valid_batch = 128 # 验证批次，取值一般为[1,256]\n",
    "displays = 100    # 显示迭代\n",
    "\n",
    "start_lr = 0.00001                         # 开始学习率，取值一般为[1e-8,5e-1]\n",
    "based_lr = 0.1                             # 基础学习率，取值一般为[1e-8,5e-1]\n",
    "epoch_iters = math.ceil(50000/train_batch) # 每轮迭代数\n",
    "warmup_iter = 10 * epoch_iters             # 预热迭代数，取值一般为[1,10]\n",
    "\n",
    "momentum = 0.9     # 优化器动量\n",
    "l2_decay = 0.00005 # 正则化系数，取值一般为[1e-5,5e-4]\n",
    "epsilon = 0.05     # 标签平滑率，取值一般为[1e-2,1e-1]\n",
    "\n",
    "checkpoint = False                   # 断点标识\n",
    "model_path = './work/out/resnet'     # 模型路径\n",
    "result_txt = './work/out/result.txt' # 结果文件\n",
    "class_num  = 10                      # 类别数量\n",
    "\n",
    "with fluid.dygraph.guard():\n",
    "    # 准备数据\n",
    "    train_reader = paddle.batch(\n",
    "        reader=paddle.reader.shuffle(reader=paddle.dataset.cifar.train10(), buf_size=50000),\n",
    "        batch_size=train_batch)\n",
    "    \n",
    "    valid_reader = paddle.batch(\n",
    "        reader=paddle.dataset.cifar.test10(),\n",
    "        batch_size=valid_batch)\n",
    "    \n",
    "    # 声明模型\n",
    "    model = ResNet()\n",
    "    \n",
    "    # 优化算法\n",
    "    consine_lr = fluid.layers.cosine_decay(based_lr, epoch_iters, epoch_num) # 余弦衰减策略\n",
    "    decayed_lr = fluid.layers.linear_lr_warmup(consine_lr, warmup_iter, start_lr, based_lr) # 线性预热策略\n",
    "    \n",
    "    optimizer = fluid.optimizer.Momentum(\n",
    "        learning_rate=decayed_lr,                           # 衰减学习策略\n",
    "        momentum=momentum,                                  # 优化动量系数\n",
    "        regularization=fluid.regularizer.L2Decay(l2_decay), # 正则衰减系数\n",
    "        parameter_list=model.parameters())\n",
    "    \n",
    "    # 加载断点\n",
    "    if checkpoint: # 是否加载断点文件\n",
    "        model_dict, optimizer_dict = fluid.load_dygraph(model_path) # 加载断点参数\n",
    "        model.set_dict(model_dict)                                  # 设置权重参数\n",
    "        optimizer.set_dict(optimizer_dict)                          # 设置优化参数\n",
    "    else:          # 否则删除结果文件\n",
    "        if os.path.exists(result_txt): # 如果存在结果文件\n",
    "            os.remove(result_txt)      # 那么删除结果文件\n",
    "    \n",
    "    # 初始训练\n",
    "    avg_train_loss = 0 # 平均训练损失\n",
    "    avg_valid_loss = 0 # 平均验证损失\n",
    "    avg_valid_accu = 0 # 平均验证精度\n",
    "    \n",
    "    iterator = 1                                # 迭代次数\n",
    "    train_prompt = \"Train loss\"                 # 训练标签\n",
    "    valid_prompt = \"Valid loss\"                 # 验证标签\n",
    "    ploter = Ploter(train_prompt, valid_prompt) # 训练图像\n",
    "    \n",
    "    best_epoch = 0           # 最好周期\n",
    "    best_accu = 0            # 最好精度\n",
    "    best_loss = 100.0        # 最好损失\n",
    "    train_time = time.time() # 训练时间\n",
    "    \n",
    "    # 开始训练\n",
    "    for epoch_id in range(epoch_num):\n",
    "        # 训练模型\n",
    "        model.train() # 设置训练\n",
    "        for batch_id, train_data in enumerate(train_reader()):\n",
    "            # 读取数据\n",
    "            image_data = np.array([x[0] for x in train_data]).reshape((-1, 3, 32, 32)).astype(np.float32) # 读取图像数据\n",
    "            image_data = train_augment(image_data)                                                        # 使用数据增强\n",
    "            image = fluid.dygraph.to_variable(image_data)                                                 # 转换数据类型\n",
    "\n",
    "            label_data = np.array([x[1] for x in train_data]).astype(np.int64)                        # 读取标签数据\n",
    "            label = fluid.dygraph.to_variable(label_data)                                             # 转换数据类型\n",
    "            label = fluid.layers.label_smooth(label=fluid.one_hot(label, class_num), epsilon=epsilon) # 使用标签平滑\n",
    "            label.stop_gradient = True                                                                # 停止梯度传播\n",
    "\n",
    "            # 前向传播\n",
    "            infer = model(image)\n",
    "            \n",
    "            # 计算损失\n",
    "            loss = fluid.layers.cross_entropy(infer, label, soft_label=True)\n",
    "            train_loss = fluid.layers.mean(loss)\n",
    "            \n",
    "            # 反向传播\n",
    "            train_loss.backward()\n",
    "            optimizer.minimize(train_loss)\n",
    "            model.clear_gradients()\n",
    "            \n",
    "            # 显示结果\n",
    "            if iterator % displays == 0:\n",
    "                # 显示图像\n",
    "                avg_train_loss = train_loss.numpy()[0]                # 设置训练损失\n",
    "                ploter.append(train_prompt, iterator, avg_train_loss) # 添加训练图像\n",
    "                ploter.plot()                                         # 显示训练图像\n",
    "                \n",
    "                # 打印结果\n",
    "                print(\"iteration: {:6d}, epoch: {:3d}, train loss: {:.6f}, valid loss: {:.6f}, valid accuracy: {:.2%}\".format(\n",
    "                    iterator, epoch_id+1, avg_train_loss, avg_valid_loss, avg_valid_accu))\n",
    "                \n",
    "                # 写入文件\n",
    "                with open(result_txt, 'a') as file:\n",
    "                    file.write(\"iteration: {:6d}, epoch: {:3d}, train loss: {:.6f}, valid loss: {:.6f}, valid accuracy: {:.2%}\\n\".format(\n",
    "                        iterator, epoch_id+1, avg_train_loss, avg_valid_loss, avg_valid_accu))\n",
    "            \n",
    "            # 增加迭代\n",
    "            iterator += 1\n",
    "            \n",
    "        # 验证模型\n",
    "        valid_loss_list = [] # 验证损失列表\n",
    "        valid_accu_list = [] # 验证精度列表\n",
    "        \n",
    "        model.eval()   # 设置验证\n",
    "        for batch_id, valid_data in enumerate(valid_reader()):\n",
    "            # 读取数据\n",
    "            image_data = np.array([x[0] for x in valid_data]).reshape((-1, 3, 32, 32)).astype(np.float32) # 读取图像数据\n",
    "            image_data = valid_augment(image_data)                                                        # 使用图像增强\n",
    "            image = fluid.dygraph.to_variable(image_data)                                                 # 转换数据类型\n",
    "            \n",
    "            label_data = np.array([x[1] for x in valid_data]).reshape((-1, 1)).astype(np.int64) # 读取标签数据\n",
    "            label = fluid.dygraph.to_variable(label_data)                                       # 转换数据类型\n",
    "            label.stop_gradient = True                                                          # 停止梯度传播\n",
    "            \n",
    "            # 前向传播\n",
    "            infer = model(image)\n",
    "            \n",
    "            # 计算精度\n",
    "            valid_accu = fluid.layers.accuracy(infer,label)\n",
    "            \n",
    "            valid_accu_list.append(valid_accu.numpy())\n",
    "            \n",
    "            # 计算损失\n",
    "            loss = fluid.layers.cross_entropy(infer, label)\n",
    "            valid_loss = fluid.layers.mean(loss)\n",
    "            \n",
    "            valid_loss_list.append(valid_loss.numpy())\n",
    "        \n",
    "        # 设置结果\n",
    "        avg_valid_accu = np.mean(valid_accu_list)             # 设置验证精度\n",
    "        \n",
    "        avg_valid_loss = np.mean(valid_loss_list)             # 设置验证损失\n",
    "        ploter.append(valid_prompt, iterator, avg_valid_loss) # 添加训练图像\n",
    "        \n",
    "        # 保存模型\n",
    "        fluid.save_dygraph(model.state_dict(), model_path)     # 保存权重参数\n",
    "        fluid.save_dygraph(optimizer.state_dict(), model_path) # 保存优化参数\n",
    "        \n",
    "        if avg_valid_loss < best_loss:\n",
    "            fluid.save_dygraph(model.state_dict(), model_path + '-best') # 保存权重\n",
    "            \n",
    "            best_epoch = epoch_id + 1                                    # 更新迭代\n",
    "            best_accu = avg_valid_accu                                   # 更新精度\n",
    "            best_loss = avg_valid_loss                                   # 更新损失\n",
    "    \n",
    "    # 显示结果\n",
    "    train_time = time.time() - train_time # 设置训练时间\n",
    "    print('complete - train time: {:.0f}s, best epoch: {:3d}, best loss: {:.6f}, best accuracy: {:.2%}'.format(\n",
    "        train_time, best_epoch, best_loss, best_accu))\n",
    "    \n",
    "    # 写入文件\n",
    "    with open(result_txt, 'a') as file:\n",
    "        file.write('complete - train time: {:.0f}s, best epoch: {:3d}, best loss: {:.6f}, best accuracy: {:.2%}\\n'.format(\n",
    "            train_time, best_epoch, best_loss, best_accu))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false
   },
   "source": [
    "### 模型预测"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {
    "collapsed": false
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "infer time: 0.008543s, infer value: horse\n"
     ]
    },
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 216x216 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "import paddle.fluid as fluid\n",
    "from PIL import Image\n",
    "import numpy as np\n",
    "import time\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "image_path = './work/out/img.png' # 图片路径\n",
    "model_path = './work/out/resnet-best' # 模型路径\n",
    "\n",
    "# 加载图像\n",
    "def load_image(image_path):\n",
    "    \"\"\"\n",
    "    功能:\n",
    "        读取图像并转换到输入格式\n",
    "    输入:\n",
    "        image_path - 输入图像路径\n",
    "    输出:\n",
    "        image - 输出图像\n",
    "    \"\"\"\n",
    "    # 读取图像\n",
    "    image = Image.open(image_path) # 打开图像文件\n",
    "    \n",
    "    # 转换格式\n",
    "    image = image.resize((32, 32), Image.ANTIALIAS) # 调整图像大小\n",
    "    image = np.array(image, dtype=np.float32) # 转换数据格式，数据类型转换为float32\n",
    "\n",
    "    # 减去均值\n",
    "    mean = np.array([0.4914, 0.4822, 0.4465]).reshape((1, 1, -1)) # cifar数据集通道平均值\n",
    "    stdv = np.array([0.2471, 0.2435, 0.2616]).reshape((1, 1, -1)) # cifar数据集通道标准差\n",
    "    \n",
    "    image = (image/255.0 - mean) / stdv # 对图像进行归一化\n",
    "    image = image.transpose((2, 0, 1)).astype(np.float32) # 数据格式从HWC转换为CHW，数据类型转换为float32\n",
    "    \n",
    "    # 增加维度\n",
    "    image = np.expand_dims(image, axis=0) # 增加数据维度\n",
    "    \n",
    "    return image\n",
    "\n",
    "# 预测图像\n",
    "with fluid.dygraph.guard():\n",
    "    # 读取图像\n",
    "    image = load_image(image_path)\n",
    "    image = fluid.dygraph.to_variable(image)\n",
    "    \n",
    "    # 加载模型\n",
    "    model = ResNet()                               # 加载模型\n",
    "    model_dict, _ = fluid.load_dygraph(model_path) # 加载权重\n",
    "    model.set_dict(model_dict)                     # 设置权重\n",
    "    model.eval()                                   # 设置验证\n",
    "    \n",
    "    # 前向传播\n",
    "    infer_time = time.time()              # 推断开始时间\n",
    "    infer = model(image)\n",
    "    infer_time = time.time() - infer_time # 推断结束时间\n",
    "    \n",
    "    # 显示结果\n",
    "    vlist = [\"airplane\", \"automobile\", \"bird\", \"cat\", \"deer\", \"dog\", \"frog\", \"horse\", \"ship\", \"truck\"] # 标签名称列表\n",
    "    print('infer time: {:f}s, infer value: {}'.format(infer_time, vlist[np.argmax(infer.numpy())]) )\n",
    "    \n",
    "    image = Image.open(image_path) # 打开图像文件\n",
    "    plt.figure(figsize=(3, 3))     # 设置显示大小\n",
    "    plt.imshow(image)              # 设置显示图像\n",
    "    plt.show()                     # 显示图像文件"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "PaddlePaddle 1.8.4 (Python 3.5)",
   "language": "python",
   "name": "py35-paddle1.2.0"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}
